#pragma once
#include <d3d11.h>
#include <d3dx11.h>
#include <xnamath.h>
#include "..\adtype\clinkedlist.h"
#include "..\camera\camera.h"
#include "..\math\matrix.h"
#include "..\import\cimport.h"
#include "..\inputhandler\cinputhandler.h"
#include "..\builder\cbuilder.h"
#include "d3dstructs.h"
#include "crenderpass.h"

namespace NEngine {

// ================================================================================================
// Direct3d class that handles all direct3d, camera movement and input for now
// ================================================================================================
class CDirect3D {

private:

    short numcams;
    CLinkedList<IUnknown*> resreferences;
    CLinkedList<CRenderPass> renderpasses;
    D3D_FEATURE_LEVEL       supftrlvl;
    ID3D11Device*           device;
    ID3D11DeviceContext*    ictx;
    IDXGISwapChain*         swapchain;
    ID3D11RenderTargetView* rtview;
    ID3D11DepthStencilView* dsview;

    // -- light related resources
    ID3D11DepthStencilView* dsviewlighting[kMaxConeLights];
    ID3D11RenderTargetView* rtviewlighting[kMaxConeLights];
    ID3D11ShaderResourceView* srviewlighting[kMaxConeLights];
    uint numsetcones;

    ID3D11SamplerState* sampler;
    ID3D11InputLayout* ilayout;
    ID3D11VertexShader* vtxshader;
    ID3D11VertexShader* vtxshdrlightpass;
    ID3D11PixelShader* psshader;
    ID3D11PixelShader* psshdrlightpass;
    ID3D11Buffer* vtxbuffer;
    ID3D11Buffer* clrbuffer;
    ID3D11Buffer* nrmbuffer;
    ID3D11Buffer* idxbuffer;
    ID3D11Buffer* bufflights;
    ID3D11Buffer* cbuffmats;
    CCamera** curcam;
    CDynamicArray<CCamera*> lightviews;
    CInputHandler* ihandler;
    SConstantBuffer cb;
    CBuildLights::SLights clightbuff;
    bool kill;
    CDynamicArray<ushort> numindices;
    CDynamicArray<Matrix4x4> wmats;
    CImportObject::SObject objects;

    // -- direct3d intialization methods
    void CreateDeviceAndSwapChain(const HWND& hwnd, const RECT& rc);
    void CreateLightingResources(const uint width, const uint height, const uint length);
    void CreateViewsAndSamplers(const RECT& hr, bool wireframe, const uint length);
    void CreateShaders();
    void CreateBuffers(const RECT& rc);
    void CreateRenderPasses(uint length);
    void GrowResourceReferences(IUnknown* res);
    void CreateRenderPass(const uint length);
    void CreateRenderPassLighting(const uint length);

    // -- compile from shader file
    bool CompileShaderFromFile(LPCWSTR filename, LPCSTR entrypoint, 
                                  LPCSTR shadermodel, ID3DBlob** blobout);

    // -- draw 
    void Draw();

    // --------------------------------------------------------------------------------------------
	// Create a resource name for the direct3d resource
	// --------------------------------------------------------------------------------------------
	#define CreateDebugResourceName_(arg)                                            \
	    do {                                                                         \
	        const char name[] = #arg;                                                \
	        arg->SetPrivateData(WKPDID_D3DDebugObjectName, sizeof(name) - 1, name);  \
	    } while(0)

    // --------------------------------------------------------------------------------------------
    // Create a resource name and debug name for the resource
    // --------------------------------------------------------------------------------------------
    #define CreateNameAndReference_(arg)    \
        do {                                \
    	    CreateDebugResourceName_(arg);  \
    		GrowResourceReferences((arg));  \
   	    } while(0)

public:

    // -- default constructor
    CDirect3D();

    // -- destructor
    ~CDirect3D();

    // -- initialize the direct3d environment
    void Init(CImportObject::SObject& objects_, 
              const CBuildLights::SLights& cbufflights_, CCamera*& curcam,
              const CDynamicArray<CCamera*>& lviews_, 
              const HWND hwnd, const RECT& rc, bool wireframe);

    // -- listen to input
    void Listen();

    // -- update the scene
    void Update();

    // -- render the scene
    void Render();

    // -- return if app is finished
    bool Finished();

    // -- Accessor methods
    ushort GetNumCameras();
    CCamera** GetCamera(); 
};

// ------------------------------------------------------------------------------------------------
// Creates a reference to the resources so we release it properly on shutdown
// ------------------------------------------------------------------------------------------------
inline void CDirect3D::GrowResourceReferences(IUnknown* res) {
    resreferences.Grow(res);
}

// ================================================================================================
// Constructor
// ================================================================================================
inline CDirect3D::CDirect3D() : supftrlvl(D3D_FEATURE_LEVEL_11_0), device(NULL), ictx(NULL), 
                                swapchain(NULL), rtview(NULL), dsview(NULL), ilayout(NULL), 
                                vtxshader(NULL), psshader(NULL), vtxbuffer(NULL),  clrbuffer(NULL), 
                                idxbuffer(NULL), bufflights(NULL), cbuffmats(NULL), curcam(NULL), 
                                ihandler(NULL), kill(false) {
}

// ================================================================================================
// Accessors
// ================================================================================================
inline ushort CDirect3D::GetNumCameras() {
    return numcams;
}

inline CCamera** CDirect3D::GetCamera() {
    return curcam;
}

// ================================================================================================
// Destructor
// ================================================================================================
inline CDirect3D::~CDirect3D() {
    while(!resreferences.EndOfList()) 
        resreferences.GetNext()->Release();
}

// =================================================================================================
// Return a flag to see if it was finished
// =================================================================================================
inline bool CDirect3D::Finished() {
    return kill;
}

} // namespace NEngine
